from __future__ import annotations

from app.data.database.components import ComponentType
from app.data.database.database import DB
from app.data.database.item_components import ItemComponent, ItemTags
from app.data.database.difficulty_modes import RNGOption

from app.events.regions import RegionType
from app.events import triggers

from app.engine import (action, banner, combat_calcs, engine, equations,
                        image_mods, item_funcs, item_system, skill_system)
from app.engine.game_state import game
from app.engine.objects.unit import UnitObject
from app.engine.exp_calculator import ExpCalcType, ExpCalculator
from app.engine.combat import playback as pb
from app.engine.movement import movement_funcs

from app.utilities import utils, static_random

import logging

# Importing methods
from app.engine.item_components.exp_components import (determine_all_defenders, 
                                                       determine_all_damaged_defenders,
                                                       determine_all_healed_defenders, 
                                                       modify_exp)

class HealTHP(ItemComponent):
    nid = 'heal_thp'
    desc = "Item heals this amount on hit"
    tag = ItemTags.UTILITY

    expose = ComponentType.Int
    value = 10

    def _get_heal_amount(self, unit, target):
        empower_heal = skill_system.empower_heal(unit, target)
        empower_heal_received = skill_system.empower_heal_received(target, unit)
        return self.value + empower_heal + empower_heal_received

    def target_restrict(self, unit, item, def_pos, splash) -> bool:
        # Restricts target based on whether any unit has < full hp
        defender = game.board.get_unit(def_pos)
        if defender and defender.get_thp() < defender.get_max_thp():
            return True
        for s_pos in splash:
            s = game.board.get_unit(s_pos)
            if s and s.get_thp() < s.get_max_thp():
                return True
        return False

    def simple_target_restrict(self, unit, item):
        return unit and unit.get_thp() < unit.get_max_thp()

    def on_hit(self, actions, playback, unit, item, target, item2, target_pos, mode, attack_info):
        heal = self._get_heal_amount(unit, target)
        true_heal = min(heal, target.get_max_thp() - target.get_thp())
        actions.append(action.ChangeTHP(target, heal))

        # For animation
        if true_heal > 0:
            playback.append(pb.HealHit(unit, item, target, heal, true_heal))
            playback.append(pb.HitSound('MapHeal', map_only=True))
            if heal >= 30:
                name = 'MapBigHealTrans'
            elif heal >= 15:
                name = 'MapMediumHealTrans'
            else:
                name = 'MapSmallHealTrans'
            playback.append(pb.HitAnim(name, target))

    def ai_priority(self, unit, item, target, move):
        if target and skill_system.check_ally(unit, target):
            max_hp = target.get_max_thp()
            missing_health = max_hp - target.get_thp()
            help_term = utils.clamp(missing_health / float(max_hp), 0, 1)
            heal = self._get_heal_amount(unit, target)
            heal_term = utils.clamp(min(heal, missing_health) / float(max_hp), 0, 1)
            return help_term * heal_term
        return 0
        
class ShoveBetter(ItemComponent):
    nid = 'shove2'
    desc = "Item shoves target on hit, applies to 0 MOV units"
    tag = ItemTags.SPECIAL

    expose = ComponentType.Int
    value = 1

    def _check_shove(self, unit_to_move, anchor_pos, magnitude):
        offset_x = utils.clamp(unit_to_move.position[0] - anchor_pos[0], -1, 1)
        offset_y = utils.clamp(unit_to_move.position[1] - anchor_pos[1], -1, 1)
        new_position = (unit_to_move.position[0] + offset_x * magnitude,
                        unit_to_move.position[1] + offset_y * magnitude)

        mcost = movement_funcs.get_mcost(unit_to_move, new_position)
        if game.board.check_bounds(new_position) and \
                not game.board.get_unit(new_position) and \
                (mcost <= 90 or skill_system.ignore_terrain(unit_to_move)):
            return new_position
        return False

    def on_hit(self, actions, playback, unit, item, target, item2, target_pos, mode, attack_info):
        if not skill_system.ignore_forced_movement(target):
            new_position = self._check_shove(target, unit.position, self.value)
            if new_position:
                actions.append(action.ForcedMovement(target, new_position))
                playback.append(pb.ShoveHit(unit, item, target))
                
    def target_restrict(self, unit, item, def_pos, splash) -> bool:
        defender = game.board.get_unit(def_pos)
        if defender and self._check_shove(defender, unit.position, self.value) and \
                not skill_system.ignore_forced_movement(defender):
            return True
        for s_pos in splash:
            s = game.board.get_unit(s_pos)
            if self._check_shove(s, unit.position, self.value) and \
                    not skill_system.ignore_forced_movement(s):
                return True
        return False
        
class JunkTargetRestrict(ItemComponent):
    nid = 'junk_target_restrict'
    desc = 'No stacking junk'
    tag = ItemTags.TARGET

    def target_restrict(self, unit, item, def_pos, splash) -> bool:
        if unit and def_pos:
            terrain_nid = game.get_terrain_nid(game.tilemap, def_pos)
            if terrain_nid != '5':
                return True
        return False